(ns main.client (:import (javax.swing JFrame JButton JLabel JPanel JTable JTextField JScrollPane BoxLayout BorderFactory Box ImageIcon)
        (java.awt.event ActionListener MouseAdapter)
		(javax.swing.table DefaultTableModel)
		(javax.swing.event TableModelEvent TableModelListener)
		(javax.swing.border EtchedBorder)
		(java.util Vector)
		(java.text DecimalFormat)
        (java.awt BorderLayout FlowLayout Component Dimension Color)
		(java.net Socket ServerSocket)
        (java.io PrintWriter)
        (java.io BufferedReader)
        (java.io InputStreamReader)
        (javax.swing Timer))
        
        (:gen-class)

(:use :reload-all utils.readbinfile utils.readDB utils.writeDB utils.misc clojure.contrib.math))

(def *host1* "localhost")
(def *port1* 3003)

(defn disable-del-button
	"Disables and enables the delete button depending on the number of rows in the page."
	[delB table]
	(if (zero? (.getRowCount (.getModel table)))
		(.setEnabled delB false)
		(.setEnabled delB true)))
	
(defn disable-nav-buttons
	"Disable and enables the navigation buttons depending on the actual page."
	[nextB prevB lastB firstB active-page rows-per-page key-count]
	(let [last-page (ceil (/ (float key-count) rows-per-page))]
		(if (zero? key-count)
			(do
				(.setEnabled prevB false)
				(.setEnabled firstB false)
				(.setEnabled nextB false)
				(.setEnabled lastB false))
			(do
				(.setEnabled prevB true)
				(.setEnabled firstB true)
				(.setEnabled nextB true)
				(.setEnabled lastB true)
				(if (== active-page 1)
					(do
						(.setEnabled prevB false)
						(.setEnabled firstB false)))
				(if (>= active-page last-page)
					(do
						(.setEnabled nextB false)
						(.setEnabled lastB false)))))))

(defn get-selected-rows
	"Returns a vector with the selected rows."
	[table]
	(let [num-rows (.getRowCount table)]
		(loop [index 0 v []]
			(if (== index num-rows)
				v
				(if (.isRowSelected table index)
					(recur (inc index) (conj v (.getValueAt table index 0)))
					(recur (inc index) v))))))

(defn con [socket]
  "Establish a connection with a server socket"
  (with-open [input (BufferedReader. (InputStreamReader. (.getInputStream socket)))
		      output (PrintWriter. (.getOutputStream socket))]    
		(let [rbf-output			(deserialize  (String. (.readLine input)))]
	     (.flush output)
           rbf-output
)))

(defn update [sock del ch add]
  "Uses socket connection to update the db"
  (let [socket (Socket. *host1* *port1*)]
  (with-open [input (BufferedReader. (InputStreamReader. (.getInputStream socket)))
              output (PrintWriter. (.getOutputStream socket))]     
		 
         (let [sendinfo  (serialize {:delete @del :change @ch 
		                                          :new @add})]
		 (.println output sendinfo))
          (.flush output))  
))


(defn refresh []
  "Updates the actual table in the GUI"
  (let [socket (Socket. *host1* *port1*)]
    (with-open [input (BufferedReader. (InputStreamReader. (.getInputStream socket)))
                output (PrintWriter. (.getOutputStream socket))]
                (let [db  (deserialize (String. (.readLine input)))
                      records   (db :records)
                      ]
                      (println  records)
                      (.flush output)
                      records
                )
           )
    )
  )
			
(defn window
  "Displays a simple GUI window using tashe Swing API."
  [title]
  (let [frame				(JFrame. title)
		
        socket              (Socket. *host1* *port1*)
        
		mainPanel			(JPanel.)
		rightPanel			(JPanel.)
		navigationPanel		(JPanel.)
		navigationContent	(JPanel.)
		leftPanelAux		(JPanel.)
		leftPanel			(JPanel.)
		searchPanel			(JPanel.)
		searchContent		(JPanel.)
		showPanel			(JPanel.)
		showContent			(JPanel.)
		addPanel			(JPanel.)
		addContent			(JPanel.)

		logo				(JLabel. (ImageIcon. (ClassLoader/getSystemResource "img/jemima.png")))
		searchNaLa			(JLabel. "Name")
		searchLoLa			(JLabel. "Location")
		pending_changes     (JLabel. " ")      

		searchName			(JTextField. 15)
		searchLocation		(JTextField. 15)

		searchButton		(JButton. "Query")
		clearButton			(JButton. "Clear search")
		showButton			(JButton. "Show rows(s)")
		addButton			(JButton. "Add rows(s)")
		commitButton		(JButton. "Save change(s)")
		nextButton			(JButton. ">>")
		prevButton			(JButton. "<<")
		lastButton			(JButton. ">|")
		firstButton			(JButton. "|<")
		deleteButton		(JButton. "Delete row(s)")
		returnButton		(JButton. "Return")
		refreshButton       (JButton. "Refresh")
		
		rbf-output          (con socket)
		
		rows-per-page		10
		
		max-pk				(ref (rbf-output :max-pk))
		records				(ref (rbf-output :records))
		original-records	(ref (rbf-output :records))
		del-record-keys		(ref [])
		record-changes		(ref [])
		new-records			(ref (sorted-map))
		
		reckeys				(get-page (keys @records) (count (keys @records)) 1 rows-per-page)
		
		fields				(rbf-output :fields)
		field-names			(get-field-names fields)
		field-sizes			(get-field-sizes fields)
		fnames				(cons "key" field-names)
		
		
		page				(ref 1)
		max_page			(ref (int (ceil (/ (float (count (keys @records))) rows-per-page))))
        
		
			
		
		table-data			(to-array-2d (get-records @records fnames reckeys))
		table-columns		(into-array fnames)

		
		
		
		
		
		
		
		table-model			(DefaultTableModel. table-data table-columns)
		
		add-table-model		(DefaultTableModel. (into-array field-names) 1)
		
		
		
		table				(JTable. table-model)
		spane				(JScrollPane. table)
		pageLabel			(JLabel. (str @page " / " @max_page))
		
		next-action  		(proxy [ActionListener][]
								(actionPerformed [event]
									(dosync (alter page inc))
									(.setText pageLabel (str @page " / " @max_page))
									(disable-nav-buttons nextButton prevButton lastButton firstButton @page rows-per-page (count (keys @records)))
									(change-page table-model @page rows-per-page @records fnames)
									(disable-del-button deleteButton table)))
		prev-action  		(proxy [ActionListener][]
								(actionPerformed [event]
									(dosync (alter page dec))	
									(.setText pageLabel (str @page " / " @max_page))
									(disable-nav-buttons nextButton prevButton lastButton firstButton @page rows-per-page (count (keys @records)))
									(change-page table-model @page rows-per-page @records fnames)
									(disable-del-button deleteButton table)))
		first-action  		(proxy [ActionListener][]
								(actionPerformed [event]
									(dosync (alter page (fn[_] 1)))
									(.setText pageLabel (str @page " / " @max_page))
									(disable-nav-buttons nextButton prevButton lastButton firstButton @page rows-per-page (count (keys @records)))
									(change-page table-model @page rows-per-page @records fnames)
									(disable-del-button deleteButton table)))
		last-action  		(proxy [ActionListener][]
								(actionPerformed [event]
									(dosync (alter page (fn[_] (int (ceil (/ (float (count (keys @records))) rows-per-page))))))
									(.setText pageLabel (str @page " / " @max_page))
									(disable-nav-buttons nextButton prevButton lastButton firstButton @page rows-per-page (count (keys @records)))
									(change-page table-model @page rows-per-page @records fnames)
									(disable-del-button deleteButton table)))
		clear-action  		(proxy [ActionListener][]
								(actionPerformed [event]
									(.setText searchName "")
									(.setText searchLocation "")
									(dosync (alter page (fn[_] 1)))
									(let [na (.getText searchName) lo (.getText searchLocation)]
									(dosync (alter records (fn[_] (search (keys @original-records) @original-records na lo)))
									(alter max_page (fn[_] (int (ceil (/ (float (count (keys @records))) rows-per-page)))))))
									(.setText pageLabel (str @page " / " @max_page))
									(disable-nav-buttons nextButton prevButton lastButton firstButton @page rows-per-page (count (keys @records)))
									(change-page table-model @page rows-per-page @records fnames)
									(disable-del-button deleteButton table)))
		search-action  		(proxy [ActionListener][]
								(actionPerformed [event]
									;(println @max-pk)
									(dosync (alter page (fn[_] 1)))

									(let [na (.getText searchName) lo (.getText searchLocation)]
									(dosync (alter records (fn[_] (search (keys @original-records) @original-records na lo)))
									(alter max_page (fn[_] (int (ceil (/ (float (count (keys @records))) rows-per-page)))))))
									(.setText pageLabel (str @page " / " @max_page))
									(disable-nav-buttons nextButton prevButton lastButton firstButton @page rows-per-page (count (keys @records)))
									(change-page table-model @page rows-per-page @records fnames)
									(disable-del-button deleteButton table)))
		delete-action  		(proxy [ActionListener][]
								(actionPerformed [event]
									(let [selected (get-selected-rows table)]
									(if (not (empty? selected))(do (dosync (alter records (fn[_] (del-records table @records selected)))
												(alter original-records (fn[_] (del-records table @original-records selected)))
												(alter del-record-keys (fn[x] (apply conj x selected)))
												(alter max_page (fn[_] (int (ceil (/ (float (count (keys @records))) rows-per-page))))))))
										(println @del-record-keys)
										(disable-nav-buttons nextButton prevButton lastButton firstButton @page rows-per-page (count (keys @records)))
										(change-page table-model @page rows-per-page @records fnames)
										(disable-del-button deleteButton table)
										(.setText pageLabel (str @page " / " @max_page)))
								        (.setText pending_changes "Unsaved changes"))
		                                )
		add-action  		(proxy [ActionListener][]
								(actionPerformed [event]
								(.setModel table add-table-model)
								(.setEnabled searchButton false)
								(.setEnabled addButton false)
								(.setEnabled commitButton false)
								(.setEnabled returnButton true)
								(.setEnabled deleteButton false)
								(.setEnabled nextButton false)
								(.setEnabled prevButton false)
								(.setEnabled lastButton false)
								(.setEnabled firstButton false)
								(.setEnabled refreshButton false)
								(.setEnabled clearButton false)
									))
		return-action  		(proxy [ActionListener][]
								(actionPerformed [event]
								(.setEnabled searchButton true)
								(.setEnabled addButton true)
								(.setEnabled returnButton false)
								(.setEnabled refreshButton true)
								(.setEnabled clearButton true)
								(.setText pending_changes "Unsaved changes")
								(let [new-r (build-record-map (.getDataVector add-table-model) field-names (inc @max-pk))] 
								
								(dosync (alter records merge (new-r :map))
										(alter original-records merge (new-r :map))
										(alter max-pk (fn[_] (new-r :max-pk)))
										(alter new-records merge (new-r :map))
										(alter max_page (fn[_] (int (ceil (/ (float (count (keys @records))) rows-per-page)))))))
								(println @new-records)
								(.setRowCount add-table-model 0)
								(.setRowCount add-table-model 1)
								(disable-nav-buttons nextButton prevButton lastButton firstButton @page rows-per-page (count (keys @records)))
								(disable-del-button deleteButton table)
								(change-page table-model @page rows-per-page @records fnames)
								(.setModel table table-model)
								(.setText pageLabel (str @page " / " @max_page))))
		m-click				(proxy [TableModelListener][]
								(tableChanged [event]
									(let [column (.getColumn event) row (.getFirstRow event)]
										(if (and (not (neg? column)) (not (neg? row)))
											(let [key (.getValueAt table row 0) content (.getValueAt table row column)]
												(dosync
													(alter record-changes conj [key column content])
													(alter records (fn[_] (change-record @records key column fnames content)))
													(alter original-records (fn[_] (change-record @original-records key column fnames content)))
												)
												(println @record-changes))))
								)
		                          )
		a-click				(proxy [TableModelListener][]
								(tableChanged [event]
								(if (and (zero? (.getColumn event)) (== (.getRowCount add-table-model) (inc (.getLastRow event))))
								(.addRow add-table-model (Vector.)))
									(.setText pending_changes "Unsaved changes")))
		
		timerAction             (proxy [ActionListener][]
		                          (actionPerformed [event]
		                            
		                        (.setEnabled commitButton false))
		                        
		                        )
		
		timer                   (Timer. 5000 timerAction)
									
									
		commit-action  		(proxy [ActionListener][]
								(actionPerformed [event]
								(update socket del-record-keys record-changes new-records)
																(dosync (alter del-record-keys (fn[_] []))
										(alter record-changes (fn[_] []))
										(alter new-records (fn[_] (sorted-map))))
								(.setText pending_changes " ")
								(.setEnabled commitButton false)
								)
									
		)
		
		
		refresh-action  		(proxy [ActionListener][]
								(actionPerformed [event]
								
								(let [refreshed (refresh)]
								
								(.restart timer)
								(.setEnabled commitButton true)
								(dosync (alter records (fn[_] refreshed))
										(alter original-records (fn[_] refreshed)))
								
								
								)
								
								(dosync (alter page (fn[_] 1))
								(alter max_page (fn[_] (int (ceil (/ (float (count (keys @records))) rows-per-page))))))
								(.setText pageLabel (str @page " / " @max_page))
								(disable-nav-buttons nextButton prevButton lastButton firstButton @page rows-per-page (count (keys @records)))
								(change-page table-model @page rows-per-page @records fnames)
								(disable-del-button deleteButton table)
								
								))
		]

	(.setLayout mainPanel (BorderLayout.))

	(.setLayout rightPanel (BorderLayout.))

	(.setLayout navigationPanel (BoxLayout. navigationPanel BoxLayout/Y_AXIS))
	(.setLayout navigationContent (BoxLayout. navigationContent BoxLayout/X_AXIS))

	(.setLayout leftPanelAux (FlowLayout.))
	(.setLayout leftPanel (BoxLayout. leftPanel BoxLayout/Y_AXIS))

	(.setLayout searchPanel (BoxLayout. searchPanel BoxLayout/Y_AXIS))
	(.setLayout searchContent (BoxLayout. searchContent BoxLayout/Y_AXIS))

	(.setLayout showPanel (BoxLayout. showPanel BoxLayout/Y_AXIS))
	(.setLayout showContent (BoxLayout. showContent BoxLayout/X_AXIS))

	(.setLayout addPanel (BoxLayout. addPanel BoxLayout/Y_AXIS))
	(.setLayout addContent (BoxLayout. addContent BoxLayout/Y_AXIS))

	;Setting Properties.

	(.setBorder mainPanel (BorderFactory/createEmptyBorder 10 10 10 10))
	(.setBorder navigationPanel (BorderFactory/createTitledBorder (BorderFactory/createEtchedBorder EtchedBorder/LOWERED) "Navigate & Delete"))
	(.setBorder navigationContent (BorderFactory/createEmptyBorder 10 10 10 10))
	(.setBorder searchPanel (BorderFactory/createTitledBorder (BorderFactory/createEtchedBorder EtchedBorder/LOWERED) "Search mode"))
	(.setBorder searchContent (BorderFactory/createEmptyBorder 10 10 10 10))
	(.setBorder showPanel (BorderFactory/createTitledBorder (BorderFactory/createEtchedBorder EtchedBorder/LOWERED) "Explore mode"))
	(.setBorder showContent (BorderFactory/createEmptyBorder 10 10 10 10))
	(.setBorder addPanel (BorderFactory/createTitledBorder (BorderFactory/createEtchedBorder EtchedBorder/LOWERED) "Add mode"))
	(.setBorder addContent (BorderFactory/createEmptyBorder 10 10 10 10))

	(.setAlignmentX logo Component/CENTER_ALIGNMENT)

	(.setAlignmentX searchNaLa Component/CENTER_ALIGNMENT)
	(.setAlignmentX searchLoLa Component/CENTER_ALIGNMENT)
	(.setAlignmentX searchName Component/CENTER_ALIGNMENT)
	(.setAlignmentX searchLocation Component/CENTER_ALIGNMENT)
	(.setAlignmentX searchButton Component/CENTER_ALIGNMENT)
	(.setAlignmentX clearButton Component/CENTER_ALIGNMENT)
	
	(.setAlignmentX addButton Component/CENTER_ALIGNMENT)
	(.setAlignmentX returnButton Component/CENTER_ALIGNMENT)

	(.setAlignmentX commitButton Component/CENTER_ALIGNMENT)
	(.setAlignmentX refreshButton Component/CENTER_ALIGNMENT)
	
	(.setAlignmentX pending_changes Component/CENTER_ALIGNMENT)
	
	(.setEnabled searchButton true)
	(.setEnabled addButton true)
	(.setEnabled commitButton false)
	(.setEnabled returnButton false)
	(.setEnabled refreshButton true)
	(.setRepeats timer false)
	(disable-nav-buttons nextButton prevButton lastButton firstButton @page rows-per-page (count (keys @records)))
	(disable-del-button deleteButton table)

	;Colors.

	(.setBackground showPanel Color/lightGray)
	(.setBackground showContent Color/lightGray)
	(.setBackground searchContent Color/lightGray)
	(.setBackground navigationContent Color/lightGray)
	(.setBackground leftPanelAux Color/lightGray)
	(.setBackground navigationPanel Color/lightGray)
	(.setBackground addContent Color/lightGray)
	(.setBackground leftPanel Color/lightGray)
	(.setBackground addPanel Color/lightGray)
	(.setBackground mainPanel Color/gray)
	(.setBackground searchPanel Color/lightGray)

	;Adding Components.

	(.add frame mainPanel)
		(.add mainPanel BorderLayout/CENTER rightPanel)
			(.add rightPanel BorderLayout/CENTER spane)
			(.add rightPanel BorderLayout/SOUTH navigationPanel)
				(.add navigationPanel navigationContent)
				(.add navigationContent firstButton)
				(.add navigationContent (Box/createRigidArea (Dimension. 10 0)))
				(.add navigationContent prevButton)
				(.add navigationContent (Box/createRigidArea (Dimension. 20 0)))
				(.add navigationContent pageLabel)
				(.add navigationContent (Box/createRigidArea (Dimension. 20 0)))
				(.add navigationContent nextButton)
				(.add navigationContent (Box/createRigidArea (Dimension. 10 0)))
				(.add navigationContent lastButton)
				(.add navigationContent (Box/createRigidArea (Dimension. 70 0)))
				(.add navigationContent deleteButton)
		(.add mainPanel BorderLayout/WEST leftPanelAux)
			(.add leftPanelAux leftPanel)
				(.add leftPanel logo)
				(.add leftPanel (Box/createRigidArea (Dimension. 0 10)))
				(.add leftPanel searchPanel)
					(.add searchContent searchNaLa)
					(.add searchContent searchName)
					(.add searchContent (Box/createRigidArea (Dimension. 0 10)))
					(.add searchContent searchLoLa)
					(.add searchContent searchLocation)
					(.add searchContent (Box/createRigidArea (Dimension. 0 10)))
					(.add searchContent searchButton)
					(.add searchContent (Box/createRigidArea (Dimension. 0 10)))
					(.add searchContent clearButton)
					(.add searchPanel searchContent)
				;(.add leftPanel showPanel)
					(.add showPanel (Box/createHorizontalGlue))
					(.add showPanel showContent)
						(.add showContent showButton)
					(.add showPanel (Box/createHorizontalGlue))
				(.add leftPanel addPanel)
					(.add addPanel (Box/createHorizontalGlue))
					(.add addPanel addContent)
						(.add addContent addButton)
						(.add addContent (Box/createRigidArea (Dimension. 0 10)))
						(.add addContent returnButton)
					(.add addPanel (Box/createHorizontalGlue))
				
			    (.add leftPanel (Box/createRigidArea (Dimension. 0 20)))
				(.add leftPanel refreshButton)
				(.add leftPanel (Box/createRigidArea (Dimension. 0 10)))
				(.add leftPanel pending_changes)
				(.add leftPanel (Box/createRigidArea (Dimension. 0 10)))
			    (.add leftPanel commitButton)
				

				
	(.addActionListener nextButton next-action)
	(.addActionListener prevButton prev-action)
	(.addActionListener firstButton first-action)
	(.addActionListener lastButton last-action)
	(.addActionListener searchButton search-action)
	(.addActionListener deleteButton delete-action)
	(.addActionListener commitButton commit-action)
	(.addActionListener addButton add-action)
	(.addActionListener returnButton return-action)
	(.addActionListener clearButton clear-action)
	(.addActionListener refreshButton refresh-action)
	(.addTableModelListener table-model m-click)
	(.addTableModelListener add-table-model a-click)
    (.setDefaultCloseOperation frame JFrame/DISPOSE_ON_CLOSE)
    (.pack frame)
    (.setVisible frame true)))
